from openid.consumer import consumer
from openid.extensions import ax 
from datetime import datetime
import urllib2, urllib
import simplejson
import logging
import web
import config

from session import _Session as SessionProvider
from session import _User as UserProvider

providers = {
             'google':'https://www.google.com/accounts/o8/id',
             }

def seconds_in_time_delta(delta):
    return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6) / 10 ** 6

class RefererController:
    
    COOKIE_REFERER = '_REFR'
    COOKIE_DESTINATION = '_DEST'
    DEFAULT_PATH = '/'
    
    @classmethod
    def save_referer_and_dest(cls):
        referer = web.ctx.env.get('HTTP_REFERER', cls.DEFAULT_PATH)
        dest = web.ctx.path or cls.DEFAULT_PATH
        web.setcookie(cls.COOKIE_REFERER, referer)
        web.setcookie(cls.COOKIE_DESTINATION, dest)
        
    @classmethod
    def clear_referer_and_dest(cls):
        web.setcookie(cls.COOKIE_REFERER, None)
        web.setcookie(cls.COOKIE_DESTINATION, None)
        
    @classmethod
    def return_to_referer(cls):
        referer = web.cookies().get(cls.COOKIE_REFERER) or cls.DEFAULT_PATH
        cls.clear_referer_and_dest()
        raise web.seeother(referer)
    
    @classmethod
    def go_to_dest(cls):
        dest = web.cookies().get(cls.COOKIE_DESTINATION) or cls.DEFAULT_PATH
        cls.clear_referer_and_dest()
        raise web.seeother(dest)
        
 
    

def need_auth(f):
    signin_path = '/signin'
    def decorator(func):
        def proxyfunc(self, *args, **kw):
            if not self.isAuthed():
                RefererController.save_referer_and_dest() # save the current status 
                raise web.seeother(signin_path)
            return func(self, *args, **kw)
        return proxyfunc
    return decorator(f)

class AuthBase: # authentication and authorization
    ''' Base Handler class provides authentication and authorization verification and actions '''
    
    SESSION_COOKIE_NAME = 'pyoidconsexsid'
    session = None
    
    def getSession(self):
        '''
        return the existing session or a new session
        '''
        if self.session is not None:
            return self.session
        # Get session id from cookie...
        sid = web.cookies().get(self.SESSION_COOKIE_NAME)
        logging.info('session_id from cookie is %s' % sid or 'empty')
        
        session = sid and SessionProvider.get_by_session_id(sid)
        if session is None:
            session = SessionProvider.create_session()
        
        self.session = session
        return session
    
    
    def getSessionDic(self):
        '''
        session dictionary data for openid to trace the authentication
        '''
        return {
                'id' : self.getSession().session_id,
                }
        
    
    def setSessionCookie(self):
        session = self.getSession()
        web.setcookie(self.SESSION_COOKIE_NAME, session.session_id, seconds_in_time_delta(session.expireAt - datetime.utcnow()))
        
    
    def getConsumer(self):
        return consumer.Consumer(self.getSessionDic(), None)
    
    
    def isAuthed(self):
        return self.isAuthenticated() and self.isAuthorized()
    
    def doAuth(self):
        if self.isAuthenticated():
            if self.isAuthorized():
                RefererController.go_to_dest() # both done... go the the tried destination
            else:
                self.doAuthorization() # authentication finished, but authorization is not 
        else:
            self.doAuthentication()
    
    def isAuthenticated(self):
        ''' check if user has signed in or not '''
        session = self.getSession()
        return session.user and True or False
    
    
    def isAuthorized(self):
        ''' check GCP permission for user who has already signed in '''
        session = self.getSession()
        if not self.isAuthenticated():
            return False
        
        return session.user.access_token and True or False
    
    
    def doAuthentication(self):
        ''' check if the session has a user signed in, if not, redirect to google to login '''
        if self.isAuthenticated(): # already signed in
            return
        oid_provider_url = providers['google']
        oid_consumer = self.getConsumer()
        try:
            request = oid_consumer.begin(oid_provider_url)
        except consumer.DiscoveryFailure:
            raise web.InternalError('Can not discovery OpenId provider %s' % oid_provider_url)
        else:
            if request is None:
                raise web.InternalError('No OpenId Service for %s' % oid_provider_url)
            else:
                
                # Attribute Exchange extension settings
                ax_req = ax.FetchRequest()
                ax_req.add(ax.AttrInfo('http://schema.openid.net/contact/email',
                            alias='email', required=True))
                request.addExtension(ax_req)
                
                trust_root = web.ctx.home
                return_to = web.ctx.home + '/openid_result'
                redirect_url = request.redirectURL(trust_root, return_to)
                raise web.seeother(redirect_url)
        
    
    def openid_result_handler(self):
        '''
         /openid_result
        '''
        session = self.getSession()
        oid_consumer = self.getConsumer()
        
        info = oid_consumer.complete(web.input(), web.ctx.home + web.ctx.path)
        display_identifier = info.getDisplayIdentifier()
        
        if info.status == consumer.SUCCESS:
            #ax extension to get the users email address
            ax_rsp = ax.FetchResponse().fromSuccessResponse(info)
            emails = ax_rsp.get('http://schema.openid.net/contact/email')
            email = emails and emails[0] or None
            
            logging.info('Signed in with: %s' % display_identifier)
            logging.info('user email address: %s' % email)
            
            user = UserProvider.get_or_insert_by_identity(display_identifier)
            if user.email != email:
                user.email = email
                user.put()
            session.user = user
            session.put()
            
            self.doAuth() # do authorization in fact
            
        elif info.status == consumer.CANCEL:
            logging.info('user denied OpenId Authentication')
            RefererController.return_to_referer()
        else:
            errmsg = 'Unknown error in performing OpenId Response'
            logging.error(errmsg)
            raise web.InternalError(errmsg)
    
    
    def doAuthorization(self):
        ''' check if we have permission to user's cloudprint contents, if not redirect to authorization step '''
        ''' pre-condition: user has authenticated (session.user is not None '''
        
        # do authentication if not signed in
        if not self.isAuthenticated():
            self.doAuthentication()
        
        session = self.getSession()
        if session.user.access_token is None:
            url = self.getOAuth2AuthorizationCodeUrl(client_id=config.GOOGLE_OAUTH2_CLIENT_ID,
                                            redirect_uri=web.ctx.home + '/oauth_result',
                                            scope=config.GOOGLE_OAUTH2_GCP_SCOPE,
                                            response_type='code')
            raise web.seeother(url)
    
    
    def oauth_result_handler(self):
        ''' /oauth_result '''
        
        # do authentication first if user not signed in
        if not self.isAuthenticated():
            self.doAuthentication()

        session = self.getSession()
        try:
            code = web.input().code # user approved access, we get a code
            logging.info('oauth code: %s' % code)
            dic = self.getOAuth2AccessToken(code, config.GOOGLE_OAUTH2_CLIENT_ID, config.GOOGLE_OAUTH2_CLIENT_SECRET, web.ctx.home + '/oauth_result')
            session.user.set_tokens(dic['access_token'], dic['expires_in'], dic['refresh_token'])
        except Exception:
            RefererController.return_to_referer()
        
        RefererController.go_to_dest()
    
    @classmethod
    def getOAuth2AuthorizationCodeUrl(cls, client_id, redirect_uri, scope, response_type):
        return 'https://accounts.google.com/o/oauth2/auth?client_id=%s&redirect_uri=%s&scope=%s&response_type=%s' % (client_id, redirect_uri, scope, response_type)
    
    @classmethod
    def getOAuth2AccessToken(cls, code, client_id, client_secret, redirect_uri):
        form = {
                'code' : code,
                'client_id':client_id,
                'client_secret':client_secret,
                'redirect_uri':redirect_uri,
                'grant_type':'authorization_code',
                }
        data = urllib.urlencode(form)
        request = urllib2.Request(url='https://accounts.google.com/o/oauth2/token', data=data)
        opener = urllib2.build_opener()
        resp = opener.open(request)
        resp_data = resp.read()
        dic = simplejson.loads(resp_data)
        return dic
    

